﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using RaytracerLib.Layers;
using RaytracerLib.MathLib;
using TomanuExtensions;
using YAXLib;
using RaytracerLib.Materials;

namespace RaytracerLib.Volumes
{
    public abstract class Volume : IMaterialPart
    {
        private Volume m_parent;

        protected Volume()
        {
        }

        public virtual Layer Layer
        {
            get
            {
                return null;
            }
            set
            {
                throw new InvalidOperationException();
            }
        }

        public virtual IEnumerable<Volume> Children
        {
            get
            {
                yield break;
            }
        }

        public virtual Volume Parent
        {
            get
            {
                return m_parent;
            }
            internal set
            {
                m_parent = value;
            }
        }

        public override int GetHashCode()
        {
            return YAXSerializer.CalculateHashCode(this);
        }

        public override bool Equals(object a_obj)
        {
            if (Object.ReferenceEquals(a_obj, this))
                return true;

            if (a_obj == null)
                return false;
            Volume volume = a_obj as Volume;
            if ((object)volume == null)
                return false;

            return YAXSerializer.Compare(this, volume);
        }

        #region Operations

        public static IList<ProceduralVolume> GetAllScripts(Volume a_volume)
        {
            return (from leaf in GetLeaves(a_volume)
                    where leaf.GetType().IsDerivedFrom(typeof(ProceduralVolume))
                    select (ProceduralVolume)leaf).ToList();
        }

        public static IList<Volume> GetLeaves(Volume a_volume)
        {
            var children = a_volume.Children;
            if (children.Count() == 0)
                return new List<Volume> { a_volume };
            else
            {
                List<Volume> result = new List<Volume>();
                foreach (var child in children)
                    result.AddRange(GetLeaves(child));
                return result;
            }
        }

        public static void InsertBefore(Material a_material, Volume a_node, Volume a_new)
        {
            if (a_node.Parent == null)
            {
                if (!a_material.MaterialParts.Contains(a_node))
                    throw new InvalidOperationException();

                a_material.ReplaceMaterialPart(a_node, a_new);

                InsertAfter(a_new, a_node);
            }
            else
                InsertAfter(a_node.Parent, a_new);
        }

        private static Volume GetChild(Volume a_volume)
        {
            UnaryVolume uv = a_volume as UnaryVolume;

            if (uv == null)
                throw new NotImplementedException();

            return uv.ChildVolume;
        }

        private static void SetChild(Volume a_volume, Volume a_child)
        {
            UnaryVolume uv = a_volume as UnaryVolume;

            if (uv == null)
                throw new NotImplementedException();

            uv.ChildVolume = a_child;
        }

        public static void InsertAfter(Volume a_node, Volume a_new)
        {
            if (a_new.Children.Any(c => c != null))
            {
                if (Volume.GetChild(a_node) != null)
                    throw new NotImplementedException();

                Volume.SetChild(a_node, a_new);
            }
            else
            {
                Volume old = Volume.GetChild(a_node);
                Volume.SetChild(a_node, a_new);
                if (old != null)
                    Volume.SetChild(a_new, old);
            }
        }

        #endregion

        #region IMaterialPart

        public Tangents GetTangents(Intersection a_intersection)
        {
            Vector3 tx, ty;
            a_intersection.SceneObject.GetTangents(a_intersection, out tx, out ty);

            Debug.Assert(tx.IsNormalized);
            Debug.Assert(ty.IsNormalized);

            return GetTangents(a_intersection.UVW,
                a_intersection.SceneObject.WorldToLocal * tx,
                a_intersection.SceneObject.WorldToLocal * ty, a_intersection);
        }

        public ColorFloat GetColor(Intersection a_intersection)
        {
            return GetColor(a_intersection.UVW, a_intersection);
        }

        public double GetValue(Intersection a_intersection)
        {
            return GetValue(a_intersection.UVW, a_intersection);
        }

        public virtual void RenderStart(RenderStartPhase a_phase)
        {
            foreach (var volume in Children)
                volume.RenderStart(a_phase);
        }

        public virtual void RenderEnd(bool a_all)
        {
        }

        #endregion

        /// <summary>
        ///
        /// </summary>
        /// <param name="a_uvw">Object space</param>
        /// <param name="a_tx">Object space</param>
        /// <param name="a_ty">Object space</param>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        public abstract Tangents GetTangents(Vector3 a_uvw, Vector3 a_tx, Vector3 a_ty,
            Intersection a_intersection);

        /// <summary>
        ///
        /// </summary>
        /// <param name="a_uvw">Object space</param>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        public abstract ColorFloat GetColor(Vector3 a_uvw, Intersection a_intersection);

        /// <summary>
        ///
        /// </summary>
        /// <param name="a_uvw">Object space</param>
        /// <param name="a_intersection"></param>
        /// <returns></returns>
        public abstract double GetValue(Vector3 a_uvw, Intersection a_intersection);
    }
}